#include <iostream>
#include <string>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <unistd.h>

#include <cstring>
#include <cstdlib>
using namespace std;

static const int default_sockfd = -1;
static const int default_retry_interval = 1;
static const int default_max_retries = 5;

enum
{
    USAGE_ERROR = 1,
    SOCKET_ERROR,
    BIND_ERROR
};

// 1.定义连接状态
// c++11强类型枚举,使用时需要带作用域Status::
// 会进行类型检查
enum class Status
{
    NEW = 1,      // 新建状态,创建一个新的套接字
    CONNECTING,   // 正在连接的状态(断线重连,正在进行连接)
    CONNECTED,    // 首次连接成功或者断线重连成功
    DISCONNECTED, // 失联状态(通信时服务端断开,此时客户端没有进行重连,此时客户端处于失联状态)
    CLOSED        // 规定:第一次连接失败时,依旧会进行重连,当重连达到一定次数,依旧失败,定义为重连失败
};

// 建立TCP连接关系(最大重连次数,重连时间间隔)
class ClientConnection
{
public:
    ClientConnection(string ip, uint16_t port)
        : _sockfd(default_sockfd), _serverip(ip), _serverport(port), _status(Status::NEW), _retry_interval(default_retry_interval), _max_retries(default_max_retries)
    {
    }

    // ClientConnection类内部每个非默认成员函数均需要维护_status
    // eg:连接成功如何更改status,连接失败如何更改status
    // 返回当前连接状态,方便其他函数维护_status
    Status ConnectionStatus()
    {
        return _status;
    }

    // 客户端向服务端发起连接
    // Connect()连接时每次均要建立套接字,一旦连接失败,进入重连逻辑Reconnect(),关闭先前创建的套接字
    // 重新创建套接字,重新进行连接,提高代码的健壮性
    void Connect()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            cerr << "create socket error" << endl;
            exit(SOCKET_ERROR);
        }
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(_serverport);
        peer.sin_addr.s_addr = inet_addr(_serverip.c_str());
        // int inet_pton(int af, const char *src, void *dst);
        // inet_pton更加安全,缓冲区由用户提供,缓冲区位置在peer当中
        // af: 1.AF_INET(IPV4) 2.AF_INET6(IPV6)
        // src:字符串风格的IP地址 dst:四字节IP地址
        // inet_pton(AF_INET,_serverip.c_str(),&(peer.sin_addr));
        // p---process n---net
        int n = connect(_sockfd, (struct sockaddr *)&peer, sizeof(peer));
        if (n < 0)
        {
            // 连接失败时不可直接终止进程,需要进行重连
            //cerr << "connect failed" << endl;

            // 1.重连时首先关闭历史上打开的的套接字
            Disconnect();

            // 2.更新连接状态
            _status = Status::DISCONNECTED;

            // 3.退出此函数
            return;
        }

        // 连接成功更新状态
        _status = Status::CONNECTED;
    }

    // 客户端掉线,重新向服务端发起连接
    void Reconnect()
    {
        _status = Status::CONNECTING;
        // 1.重连必定伴随循环 2.重连必须记录当前的重连次数
        int cnt = 0;
        while (true)
        {

            Connect();
            // 检查重连的连接状态
            if (_status == Status::CONNECTED)
            {
                break;
            }
            cnt++;
            if (cnt > _max_retries)
            {
                cout << "重连失败,请检查网络连接状态..." << endl;
                _status = Status::CLOSED;
                break;
            }
            printf("断线重连中,正在进行第%d次重新连接\n", cnt);
            sleep(_retry_interval); // 重连时间间隔
        }
    }

    // IO通信&&业务处理
    void Process()
    {
        while(true)
        {
            string message="Hello Linux!";
            ssize_t n = send(_sockfd,message.c_str(),message.size(),0);
            if(n>0)
            {
                char buffer[1024];
                ssize_t m=recv(_sockfd,buffer,sizeof(buffer)-1,0);
                if(m>0)
                {
                    buffer[m]=0;
                    cout<<"[server echo]# "<<buffer<<endl;
                }
                else
                {
                    //1.服务端关闭连接或者服务器掉线 2.读取出错
                    _status=Status::DISCONNECTED;
                    break;
                }

            }
            else
            {
                cout<<"send error"<<endl;
                _status=Status::CLOSED;
                break;
            }
            sleep(2);
        }
    }

    // 重连失败,关闭连接
    void Disconnect()
    {
        if (_sockfd > default_sockfd)
        {
            ::close(_sockfd);
            _sockfd = default_sockfd;
            _status = Status::CLOSED;
        }
    }

    ~ClientConnection()
    {
    }

private:
    int _sockfd;          // 服务端通信的套接字
    string _serverip;     // 服务端的ip地址
    uint16_t _serverport; // 服务端的端口号
    Status _status;       // 连接状态
    int _retry_interval;  // 重连的时间间隔
    int _max_retries;     // 最大重连次数
};

// Excute()实现状态机
class TcpClient
{
public:
    TcpClient(string serverip, uint16_t serverport)
        : _connection(serverip, serverport)
    {
    }
    // 服务连接服务器,与服务器进行通信
    void Excute()
    {
        // 通过获取连接状态从而决定执行何种措施
        while (true)
        {
            switch (_connection.ConnectionStatus())
            {
            case Status::NEW:
                // 新建状态,表明只调用ClientConnection()构造函数,下一步应该发起连接
                _connection.Connect();
                break;
            case Status::CONNECTED:
                // 1.连接成功,客户端与服务端进行IO通信
                _connection.Process();
                break;
            case Status::DISCONNECTED:
                // 1.客户端首次连接失败/重连失败
                // 2.客户端与服务端正常进行IO通信,服务端被终止
                _connection.Reconnect();
                // 重连的两种可能: 1.重连到未达到最大连接次数时成功连接服务端 2.重连直接达到最大重连次数依旧失败
                break;
            case Status::CLOSED:
                // 重连失败关闭连接直接终止客户端进程
                _connection.Disconnect();
                return;
            default:
                break;
            }
        }
    }

    ~TcpClient()
    {
    }

private:
    ClientConnection _connection;
};

void Usage(string proc)
{
    cout << "Usage:\r\n"
         << proc << " serverip serverport" << endl;
}

//./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }
    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);

    TcpClient tcpclient(serverip, serverport);
    tcpclient.Excute();

    return 0;
}